Skip to content

feat(0.2): suppression model — .terrain/suppressions.yaml (Track 4.5)#139

Open
pmclSF wants to merge 1 commit intofeat/0.2-finding-idsfrom
feat/0.2-suppressions
Open

feat(0.2): suppression model — .terrain/suppressions.yaml (Track 4.5)#139
pmclSF wants to merge 1 commit intofeat/0.2-finding-idsfrom
feat/0.2-suppressions

Conversation

@pmclSF
Copy link
Copy Markdown
Owner

@pmclSF pmclSF commented May 2, 2026

Summary

Track 4.5 from the 0.2.0 parity-gated release plan. Brings forward what the prior plan deferred to 0.3: a working suppression model in 0.2.0 so adopters can adopt strict CI gating without forking the project. Builds on Track 4.4 (stable finding IDs).

Base branch: `feat/0.2-finding-ids` (PR #138). When that merges to main, this PR rebases automatically.

Schema

```yaml
schema_version: "1"
suppressions:

  • finding_id: weakAssertion@internal/auth/login.go:TestLogin#a1b2c3d4
    reason: false positive; sanitized upstream
    expires: 2026-08-01
    owner: "@platform"

  • signal_type: aiPromptInjectionRisk
    file: internal/legacy/**
    reason: rewriting in 0.3
    expires: 2026-09-01
    ```

Match modes

Each entry uses exactly one of two:

  • `finding_id` exact match — survives line drift when the underlying signal has a stable symbol (per Track 4.4)
  • `signal_type` + `file` glob — coarser class-wide match; supports `**` recursive patterns

Lifecycle

  • `reason` is required — every suppression justifies itself
  • `expires` optional ISO 8601 date — after the date, the suppression is INVALID, the underlying signal fires again, AND a `suppressionExpired` warning surfaces so silent rot doesn't accumulate
  • `owner` optional free-text pointer

What landed

  • `internal/suppression/` — Load + Apply + path-glob helpers. 9 unit tests.
  • `internal/engine/pipeline.go` — Step 10c after FindingID assignment.
  • `PipelineOptions.SuppressionsPath` for `terrain analyze --suppressions ` (CLI flag follows in a separate small PR).
  • `internal/engine/suppressions.go` — wires loading + application + expired-warning emission.
  • `internal/engine/suppressions_test.go` — 5 integration tests.
  • New `suppressionExpired` signal: registered in manifest + catalog; auto-generated rule doc.

What's NOT in this PR (follow-ups)

  • Track 4.6: `terrain explain finding `
  • Track 4.7: `terrain suppress ` writer
  • Track 4.8: `--new-findings-only --baseline `

Test plan

  • `go test ./internal/suppression/` — 9 tests green (load validation, expiry, finding-id match, signal-type+glob match, idempotency, nil-safety, recursive globs)
  • `go test ./internal/engine/` — 5 new integration tests green
  • `go test ./...` full suite green
  • `make docs-verify` — manifest + severity rubric + rule docs in sync

Plan link

`/Users/pzachary/.claude/plans/kind-mapping-turing.md` (Track 4.5).

🤖 Generated with Claude Code

Brings forward what the prior plan deferred to 0.3: a working
suppression model in 0.2.0 so adopters can adopt strict CI gating
without forking the project. Builds on Track 4.4 (stable finding
IDs) — most suppressions match by FindingID; a (signal_type, file
glob) fallback covers class-wide waivers.

Schema:

  schema_version: "1"
  suppressions:
    - finding_id: weakAssertion@internal/auth/login.go:TestLogin#a1b2c3d4
      reason: false positive; sanitized upstream
      expires: 2026-08-01
      owner: "@platform"

    - signal_type: aiPromptInjectionRisk
      file: internal/legacy/**
      reason: rewriting in 0.3
      expires: 2026-09-01

Match modes (an entry uses exactly one):

  * `finding_id` exact match — most precise; survives line drift
    when the underlying signal has a stable symbol per
    BuildFindingID semantics
  * `signal_type` + `file` glob — coarser; supports `**`-style
    recursive patterns. Useful for class-wide waivers.

Anti-goal: suppressions are NOT a free-form ignore-everything
switch. The schema rejects entries that satisfy neither mode and
entries missing `reason` (every suppression must justify itself).

Lifecycle:

  * `reason` required — printed when a suppressed signal would
    otherwise have been blocking, so reviewers see the rationale
    in PR comments without opening the YAML.
  * `expires` optional ISO 8601 date. After the date, the
    suppression is INVALID — the underlying signal fires again,
    and a new `suppressionExpired` warning signal surfaces in
    the report so silent rot doesn't accumulate.
  * `owner` optional free-text owner pointer for review.

Engine wiring:

  * `internal/suppression/` package — Load + Apply + path-glob
    helpers. 9 unit tests covering load validation, expiry,
    finding-id match, signal-type+glob match, idempotency, nil-
    safety.
  * `internal/engine/pipeline.go` — Step 10c after FindingID
    assignment: load `.terrain/suppressions.yaml` (or
    PipelineOptions.SuppressionsPath override), apply matched
    entries, surface expired entries as warning signals.
  * `PipelineOptions.SuppressionsPath` for `terrain analyze
    --suppressions <path>`.
  * 5 engine integration tests: drops matching signal, expired
    emits warning + lets signal fire, missing file is a no-op,
    malformed file logs and continues (don't fail pipeline on a
    fat-fingered YAML edit), override path honored.

Manifest:

  * New `suppressionExpired` signal type, governance category,
    medium severity, evidence-strong (it's a deterministic check).
    Registered in `internal/signals/manifest.go` and
    `internal/models/signal_catalog.go`. Rule doc auto-generated
    via cmd/terrain-docs-gen.
  * No new detector — pipeline emits the signal directly.

What's NOT in this PR (follow-ups):

  * Track 4.6: `terrain explain finding <id>` — round-trips an ID
    back to the underlying signal + suggests a suppression command
  * Track 4.7: `terrain suppress <id>` — writes a suppression
    entry to the YAML with goccy/go-yaml round-trip preservation
  * Track 4.8: `--new-findings-only --baseline <path>` — uses the
    same FindingID set to filter signals against a baseline

Verification:
  go test ./internal/suppression/ — 9 tests green
  go test ./internal/engine/ — 5 new integration tests green
  go test ./... — full suite green
  make docs-verify — manifest + severity rubric + rule docs in sync

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant